#include "NSPropertyList.h"
#include "NSArray.h"
#include "NSDictionary.h"
#include "NSData.h"

#include "wx/xml/xml.h"
#include "wx/mstream.h"
#include "wx/sstream.h"

#include <iostream>

class PlistParser
{
public:
	static bool isSupported(const void *data, size_t len);
	PlistParser() {}
	wxObject* root();
};


struct binaryPlistTail
{
	wxUint8 unknwn[6];   /* 00-05 */
	wxUint8 off_size;    /* 06    */
	wxUint8 ind_size;    /* 07    */
	wxUint8 unknwn2[20]; /* 08-27 */
	wxUint32 tbl_offset; /* 28-31 */
};

#define TAIL_LEN (sizeof(struct binaryPlistTail))

class BinaryParser : public PlistParser
{
public:
	static bool isSupported(const void *data, size_t len)
	{
		return len > 8 && !memcmp(data, "bplist00", 8);
	}
	BinaryParser(const void *data, size_t len);
	wxObject* root() {return objectAtIndex(0);}
protected:
	wxUint32 offsetForIndex(wxUint32 index);
	wxObject* objectAtIndex(wxUint32 index);
	wxUint32 readCount(wxUint32* counter);
	wxUint32 readObjectIndex(wxUint32 *counter);
private:
	const wxUint8 *data;
	size_t len;
	const wxUint8 *table;
	wxUint8 off_size, ind_size;
	size_t tbl_len;
};

BinaryParser::BinaryParser(const void *_data, size_t _len) : PlistParser()
{
	wxASSERT(TAIL_LEN==32); /* in case of messed up padding */
	data = (const wxUint8*)_data;
	len = _len;

	wxASSERT(len > TAIL_LEN);
	len -= TAIL_LEN;
	const struct binaryPlistTail* tail = (const binaryPlistTail*)(data + len);
	off_size = tail->off_size;
	ind_size = tail->ind_size;
	wxUint32 tbl_offset = wxUINT32_SWAP_ON_LE(tail->tbl_offset);
	wxASSERT(off_size==1 || off_size==2 || off_size==4);
	wxASSERT(ind_size==1 || ind_size==2 || ind_size==4);
	wxASSERT(tbl_offset < len);
	tbl_len = len - tbl_offset;
	table = data + tbl_offset;
	len = tbl_offset;
}

wxUint32 BinaryParser::offsetForIndex(wxUint32 index)
{
	wxASSERT(index<tbl_len);
	index *= off_size;
	switch(off_size)
	{
	case 1:
		return table[index];
	case 2:
		return wxUINT16_SWAP_ON_LE(*(wxUint16*)(table+index));
	case 4:
		return wxUINT32_SWAP_ON_LE(*(wxUint32*)(table+index));
	default:
		wxASSERT(false); /* can't be */
		return 0;
	}
}

static inline float uint32_to_float(wxUint32 input)
{
	union fconv
	{
		float output;
		wxUint32 input;
	} conv;
	conv.input = wxUINT32_SWAP_ON_LE(input);
	return conv.output;
}

static inline double uint64_to_double(wxUint64 input)
{
	union fconv
	{
		double output;
		wxUint64 input;
	} conv;
	conv.input = wxUINT64_SWAP_ON_LE(input);
	return conv.output;
}

wxUint32 BinaryParser::readObjectIndex(wxUint32 *counter)
{
	switch(ind_size)
	{
	case 1:
		*counter += 1;
		wxASSERT(*counter<=len);
		return data[*counter-1];
	case 2:
		*counter += 2;
		wxASSERT(*counter<=len);
		return wxUINT16_SWAP_ON_LE(*(wxUint16*)(data+*counter-2));
	case 4:
		*counter += 4;
		wxASSERT(*counter<=len);
		return wxUINT32_SWAP_ON_LE(*(wxUint32*)(data+*counter-4));
	default:
		wxASSERT(0);
		return 0;
	}
}

wxUint32 BinaryParser::readCount(wxUint32 *counter)
{
	wxASSERT(*counter<len);
	wxUint8 l1 = data[(*counter)++];
	switch(l1)
	{
	case 0x10:
		*counter += 1;
		wxASSERT(*counter<=len);
		return data[*counter-1];
	case 0x11:
		*counter += 2;
		wxASSERT(*counter<=len);
		return wxUINT16_SWAP_ON_LE(*(wxUint16*)(data+*counter-2));
	case 0x12:
	case 0x13:
		*counter += 4;
		wxASSERT(*counter<=len);
		return wxUINT32_SWAP_ON_LE(*(wxUint32*)(data+*counter-4));
	default:
		wxASSERT(0);
		return 0;
	}
}

wxObject* BinaryParser::objectAtIndex(wxUint32 index)
{
	wxUint32 counter = offsetForIndex(index);
	wxASSERT(counter<len);
	wxUint8 next = data[counter++];
	switch(next)
	{
	case 0x08: /* BOOL false */
		return new wxVariant(false);
	case 0x09: /* BOOL true */
		return new wxVariant(true);
	case 0x10: /* UINT8 */
		wxASSERT(counter<len);
		return new wxVariant((long)data[counter]);
	case 0x11: /* UINT16 */
		wxASSERT(counter+1<len);
		return new wxVariant((long)wxUINT16_SWAP_ON_LE(*(wxUint16*)(data+counter)));
	case 0x12: /* UINT32 */
		wxASSERT(counter+3<len);
		return new wxVariant((long)wxUINT32_SWAP_ON_LE(*(wxUint32*)(data+counter)));
	case 0x13: /* UINT64 */
		{
			wxASSERT(counter+3<len);
			wxUint64 l = wxUINT64_SWAP_ON_LE(*(wxUint64*)(data+counter));
			wxASSERT(l < 0x80000000L || l>=0xFFFFFFFF80000000L); /* no long long support yet */
			return new wxVariant((long)(unsigned long)l);
		}
	case 0x14:
	case 0x15:
	case 0x16:
		wxASSERT(0); /* TODO: very long support */
	case 0x22: /* float */
		wxASSERT(counter+3<len);
		return new wxVariant(uint32_to_float(*(wxUint32*)(data+counter)));
	case 0x23: /* double */
		wxASSERT(counter+7<len);
		return new wxVariant(uint64_to_double(*(wxUint64*)(data+counter)));
	case 0x80:
		wxASSERT(counter<len);
		return new NSDictionary(new wxString(wxT("CF$UID")), new wxVariant((long)data[counter]));
	case 0x81:
		wxASSERT(counter+1<len);
		return new NSDictionary(new wxString(wxT("CF$UID")), 
								new wxVariant((long)wxUINT16_SWAP_ON_LE(*(wxUint16*)(data+counter))));
	default:
		if (next>=0x40 && next<=0x4F)
		{ /* binary data */
			wxUint32 dlen = next-0x40;
			if (dlen==0xF)
				dlen = readCount(&counter);
			wxASSERT(counter+dlen<=len);
			return new NSData(data+counter, dlen);
		} else if (next>=0x50 && next<=0x5F)
		{ /* UTF-8 string */
			wxUint32 dlen = next-0x50;
			if (dlen == 0xF)
				dlen = readCount(&counter);
			wxASSERT(counter+dlen<=len);
			return new wxVariant(wxString::FromUTF8((const char *)(data+counter), dlen));
		} else if (next>=0x60 && next<=0x6F)
		{ /* BE unicode-16 string */
			wxUint32 dlen = next-0x60;
			if (dlen == 0xF)
				dlen = readCount(&counter);
			dlen = 2*dlen;
			wxASSERT(counter+dlen <= len);
			size_t olen;
			wxWCharBuffer buf(wxMBConvUTF16BE().cMB2WC((const char*)(data+counter), dlen, &olen));
			return new wxVariant(wxString(buf.data(), wxConvLibc, olen));
		} else if (next>=0xA0 && next<=0xAF)
		{ /* array */
			wxUint32 dlen = next-0xA0;
			if (dlen == 0xF)
				dlen = readCount(&counter);
			wxObject* *objects = new wxObject*[dlen];
			for(wxUint32 i=0;i<dlen;i++)
			{
				objects[i] = objectAtIndex(readObjectIndex(&counter));
			}
			return new NSArray(objects, dlen); /* inherits the array */
		} else if (next>=0xD0 && next<=0xDF)
		{ /* dictionary */
			wxUint32 dlen = next-0xD0;
			if (dlen == 0xF)
				dlen = readCount(&counter);
			wxString* *keys = new wxString*[dlen];
			wxObject* *values = new wxObject*[dlen];
			for(wxUint32 i=0;i<dlen;i++)
			{
				wxObject *o = objectAtIndex(readObjectIndex(&counter));
				wxASSERT(o);
				wxVariant *v = wxDynamicCast(o, wxVariant);
				wxASSERT(v);
				keys[i] = new wxString(v->GetString());
				delete v;
			}
			for(wxUint32 i=0;i<dlen;i++)
			{
				values[i] = objectAtIndex(readObjectIndex(&counter));
			}
			return new NSDictionary(keys, values, dlen); /* inherits the arrays */
		} else {
			wxASSERT(0);
			return NULL;
		}
	}
}

void decodebase64(void* &data, size_t& length, const wxString& input)
{
	static const wxString base64arr = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
	wxUint8 *output = new wxUint8[input.Len()*6/8];
	size_t pos = 0, hold = 0;
	wxUint8 buf = 0;
	for(size_t i = 0; i<input.Len();i++)
	{
		wxChar inc = input[i];
		if (wxIsspace(inc))
			continue;
		size_t ind = base64arr.find(inc);
		if (ind == 64) break; // padding
		wxASSERT(ind < 64);
		size_t shl = (hold <= 2) ? 6 : (8 - hold);
		buf<<=shl; buf |=(wxUint8)(ind>>(6-shl));
		hold += shl;
		if (hold == 8)
		{
			output[pos++] = buf;
			hold = 6-shl;
			buf = (wxUint8)(ind & ((1<<hold) - 1));
		}
	}
	length = pos;
	data = output;
}


class XmlParser : public PlistParser
{
public:
	static bool isSupported(const void *data, size_t len)
	{
		return len > 2 && !memcmp(data, "<?", 2);
	}
	XmlParser(const void *data, size_t len);
	wxObject* root();
protected:
/*	wxObject* objectAtIndex(wxUint32 index);
	wxUint32 offsetForIndex(wxUint32 index);
	wxUint32 readCount(wxUint32* counter);
	wxUint32 readObjectIndex(wxUint32 *counter);*/
	wxObject* parseNode(wxXmlNode* node);
private:
	wxMemoryInputStream _input;
	wxXmlDocument *_xml;
};

XmlParser::XmlParser(const void *data, size_t len) : _input(data, len)
{
	_xml = new wxXmlDocument(_input);
}

wxObject* XmlParser::root()
{
	wxXmlNode* _root = _xml->GetRoot();
	wxASSERT(_root);
	wxASSERT(_root->GetType() == wxXML_ELEMENT_NODE && _root->GetName() == wxT("plist"));
	wxXmlNode* _rootnode = _root->GetChildren();
	if (!_rootnode)
		return NULL;
	return parseNode(_rootnode);
}

wxObject* XmlParser::parseNode(wxXmlNode* node)
{
	wxASSERT(node->GetType() == wxXML_ELEMENT_NODE);
	if (node->GetName() == wxT("dict"))
	{
		NSDictionary *dict = new NSDictionary();
		node = node->GetChildren();
		while(node)
		{
			wxASSERT(node->GetType() == wxXML_ELEMENT_NODE && node->GetName() == wxT("key"));
			wxString key = node->GetNodeContent();
			node = node->GetNext();
			wxASSERT(node != NULL);
			wxASSERT(node->GetType() == wxXML_ELEMENT_NODE);
			wxObject* o = parseNode(node);
			node = node->GetNext();
			dict->Insert(key, o);
		}
		return dict;
	}
	else if (node->GetName() == wxT("array"))
	{
		NSArray* arr = new NSArray();
		node = node->GetChildren();
		while(node)
		{
			wxASSERT(node->GetType() == wxXML_ELEMENT_NODE);
			arr->addObject(parseNode(node));
			node = node->GetNext();
		}
		return arr;
	} else if (node->GetName() == wxT("data"))
	{
		void *data;
		size_t length;
		decodebase64(data, length, node->GetNodeContent());
		wxObject* o = new NSData(data, length);
		delete data;
		return o;
	} else if (node->GetName() == wxT("string"))
	{
		return new wxVariant(node->GetNodeContent());
	} else if (node->GetName() == wxT("integer"))
	{
		long l;
		if (!node->GetNodeContent().ToLong(&l))
			wxFAIL;
		return new wxVariant(l);
	} else if (node->GetName() == wxT("real"))
	{
		double d;
		if (!node->GetNodeContent().ToDouble(&d))
			wxFAIL;
		return new wxVariant(d);
	} else if (node->GetName() == wxT("true"))
	{
		return new wxVariant(true);
	} else if (node->GetName() == wxT("false"))
	{
		return new wxVariant(false);
	} else
	{
		wxASSERT(false);
		return NULL;
	}
}


wxObject* NSPropertyList::initWithData(const void *data, size_t len)
{
	if (BinaryParser::isSupported(data, len))
	{
		return BinaryParser(data, len).root();
	} else if (XmlParser::isSupported(data, len))
	{
		return XmlParser(data, len).root();
	} else
	{
		wxASSERT(false);
		return NULL;
	}
}

class XmlGenerator
{
public:
	XmlGenerator(wxObject* root);
	wxString produce();
private:
	wxString genNode(wxObject* obj);
	wxString escape(const wxString& src);
	wxString _str;
};

wxString XmlGenerator::genNode(wxObject* obj)
{
	wxString result;
	NSDictionary* dict = wxDynamicCast(obj, NSDictionary);
	if (dict)
	{
		result = wxT("<dict>");
		for(dictionary_map::const_iterator i = dict->_map->begin(); i != dict->_map->end(); i++)
		{
			result.Append(wxT("<key>"));
			result.Append(escape(i->first));
			result.Append(wxT("</key>"));
			result.Append(genNode(i->second));
		}
		result.Append(wxT("</dict>"));
		return result;
	}
	NSMutableArray* arr = wxDynamicCast(obj, NSMutableArray);
	if (arr)
	{
		result = wxT("<array>");
		for(size_t i=0; i < arr->length(); i++)
		{
			result.Append(genNode(arr->getObject(i)));
		}
		result.Append(wxT("</array>"));
		return result;
	}
	wxVariant *v = wxDynamicCast(obj, wxVariant);
	if (v)
	{
		if (v->IsType(wxT("string")))
		{
			result.Printf(wxT("<string>%s</string>"), escape(v->GetString()));
			return result;
		} else if (v->IsType(wxT("long")))
		{
			result.Printf(wxT("<integer>%d</integer>"), v->GetLong());
			return result;
		} else if (v->IsType(wxT("double")))
		{
			result.Printf(wxT("<real>%f</real>"), v->GetDouble());
			return result;
		} else if (v->IsType(wxT("bool")))
		{
			if (v->GetBool())
				result = wxT("<true />");
			else
				result = wxT("<false />");
			return result;
		} else
			wxASSERT(false);
	}
	wxASSERT(false);
	return result;
}

XmlGenerator::XmlGenerator(wxObject* root)
{
	_str = wxT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
	_str.Append(wxT("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"));
	_str.Append(wxT("<plist version=\"1.0\">\n"));
	_str.Append(genNode(root));
	_str.Append(wxT("\n</plist>"));
}

wxString XmlGenerator::produce()
{
	return _str;
}

wxString XmlGenerator::escape(const wxString& src)
{
	wxString output;
	for(size_t i = 0;i < src.Len();i++)
	{
		wxChar c = src[i];
		if (c=='&')
			output.Append(wxT("&amp;"));
		else if (c == '<')
			output.Append(wxT("&lt;"));
		else if (c == '>')
			output.Append(wxT("&gt;"));
		else if (c == '\'')
			output.Append(wxT("&apos;"));
		else if (c == '"')
			output.Append(wxT("&quot;"));
		else
			output.Append(c);
	}
	return output;
}

wxString NSPropertyList::toPlist(wxObject* root)
{
	return XmlGenerator(root).produce();
}